/*
>> StackOp.s
>>
>> Stack-based operation, allowing buffer and scalar inputs, and
>> buffer outputs. Adapted from NeighbourhoodToPixelOp.s
>>
>> Author: Wouter Caarls
>> Date:   23-06-2005
*/

// For kernel:
//   Must have equal number of inputs and output streams
//   Uses enqueue operation to add stuff to stack

// For skeleton:
//   First argument is stack (binary image for stack initialization)
//   Second argument is stack threshold
//   Third argument is input image
//   Fourth argument is output image

#include "stdstratego.h"
#include "stdsc.h"
#include "kernelsplit.h"

void *memset(void *s, int c, int n);

stratego code makeabsolute(code param, code paramptr, code type, code stride, code body)
{
  strategies
    main = <try(alltd(MakeAbsolute))>(body)

  rules
    MakeAbsolute:
      ArrayIndex(ArrayIndex(p, i1), i2) ->
      c:exp |[ ~exp:paramptr[~exp:i1 * ~exp:stride + ~exp:i2] ]|
      where (<id>(param) => p;
	     <id>(type) => IntConst("0")
	    )

    MakeAbsolute:
      p -> paramptr
      where (<id>(param) => p;
	     <id>(type) => IntConst("1")
	    )}

void addtostack(void *_item)
{
  _stack[_wp] = _item;
  _wp++;
  if (_wp == _stacksize)
    _wp = 0;


  if (_wp == _rp)
    printf("Stack overflow\n");
}

void OptStackOp2(argcode_t *argcode, int arguments, code body)
{	
int _ii, _stride, _lines, _line;
int _rp, _wp, _stacksize;
code splitbody[];

/* Check arguments */
if (arguments != 4)
  error("Must have 4 arguments");

if (argcode[0].argtype != ARG_STREAM_IN)
  error("First argument must be an input stream");

if (argcode[1].argtype != ARG_SCALAR_IN)
  error("Second argument must be an input scalar");

if (argcode[2].argtype != ARG_STREAM_IN)
  error("Third argument must be an input stream");

if (argcode[3].argtype != ARG_STREAM_OUT)
  error("Fourth argument must be an output stream");
  
if (!@equals(argcode[2].datatype, argcode[3].datatype))
  error("Input and output datatypes must match");

/* Get stream strides */
/* NOTE: ASSUMES ALL STRIDES ARE EQUAL */
_stride = scuBufferGetStride((@argcode[0].argid).buffer);
_lines = scuBufferGetLines((@argcode[0].argid).buffer);

_stacksize = _stride*_lines+1;

/* Allocate stack */
@declare(addpointer(addpointer(argcode[2].datatype)), `_stack`);
assert(_stack = @cast(addpointer(addpointer(argcode[2].datatype)),
		     malloc(_stacksize*4))); 

/* Declare arguments */
@declare(addpointer(argcode[0].datatype), `_init`);
@declare(argcode[1].datatype, `threshold`);
@declare(addpointer(argcode[2].datatype), `_image`);

if (@dataclass(argcode[1].datatype) == DATACLASS_INTEGER)
  threshold = (@argcode[1].argid).data.d;
else if (@dataclass(argcode[1].datatype) == DATACLASS_FLOAT)
  threshold = (@argcode[1].argid).data.f;
else
  error("Second argument must be scalar");

assert((_image = @cast(addpointer(argcode[2].datatype),
		       malloc(_stride*_lines*sizeof(argcode[2].datatype)))));

/* Declare pointer to current stack item */
@declare(addpointer(argcode[2].datatype), `_stackptr`);

/* Rewrite body */
body = makeabsolute(argcode[2].id, `_stackptr`, argcode[2].argtype, _stride, body);
body = makeabsolute(argcode[3].id, `_stackptr`, argcode[3].argtype, _stride, body);

splitbody = @kernelsplit(body);

/* Load input and initialise stack */
_line = 0;
_wp = 0;
while (scuBufferPeek((@argcode[0].argid).buffer, (@argcode[0].argid).reader,
		     (void**)&_init, _stride*sizeof(argcode[0].datatype), SCU_BUF_BLOCK_ALL) == _stride*sizeof(argcode[0].datatype))
{
  _stackptr = &_image[_line*_stride];

  scuBufferRead((@argcode[2].argid).buffer, (@argcode[2].argid).reader,
		(void*)_stackptr, _stride*sizeof(argcode[2].datatype), SCU_BUF_BLOCK_ALL);

  /* Create border */
  if (_line >= -argcode[2].extents[0].start && _line <= _lines-argcode[2].extents[0].end)
  {
    /* Initialise stack */
    for (_ii=-argcode[2].extents[1].start; _ii < _stride-argcode[2].extents[1].end; _ii++)
      if (_init[_ii] > threshold)
      {
	_stack[_wp] = &_stackptr[_ii];
	_wp++;
      }
  }

  _line++;

  scuBufferReleasePeeked((@argcode[0].argid).buffer, (@argcode[0].argid).reader, _stride*sizeof(argcode[0].datatype));
}

scuBufferRemoveReference((@argcode[0].argid).buffer);
scuBufferRemoveReference((@argcode[2].argid).buffer);

@splitbody[0];

printf("initial stack size %d\n", _wp);

/* Actual stack processing */
_rp = 0;
while (_rp != _wp)
{
  _stackptr = _stack[_rp];

  @splitbody[1];

  _rp++;
  if (_rp == _stacksize)
    _rp = 0;
}

@splitbody[2];

/* Write output */
for (_line=0; _line < _lines; _line++)
{
  scuBufferWrite((@argcode[3].argid).buffer, (void*)&_image[_line*_stride],
		 _stride*sizeof(argcode[3].datatype), SCU_BUF_BLOCK_ALL);
}

/* Finalize outputs */
scuBufferFinalize((@argcode[3].argid).buffer);

// Free stuff
free(_stack);
free(_image);

  /* Set properties */
  propEnvReset();
  propEnv("operation", operation);
 
  for (_ii=0; _ii < arguments; _ii++)
  {
    propEnv("argument", @idtostring(makeid(`id`, _ii)));
      propSeti("id", _ii);
      propEnv("target", target);

      propSeti("buffer", 0);
      propSeti("delay", 0);

      propSeti("elementsize", sizeof(argcode[_ii].datatype));

      if (argcode[_ii].argtype == ARG_STREAM_IN || argcode[_ii].argtype == ARG_STREAM_OUT)
      {
        propSeti("state", 1024);
        propSeti("chunksize", 1024);
      }
      else
      {
        propSeti("state", 1024*1024);
        propSeti("chunksize", 1);
      }

      propSets("timedist", "bulk");

      propEnvBack();
    propEnvBack();
  }
}
